#include "catalog/schema.h"

#include <sstream>
#include <string>
#include <vector>

#include "json/json.h"

const static Json::StaticString SCHEMA_LENGTH("length");
const static Json::StaticString SCHEMA_COLUMNS("columns");
const static Json::StaticString SCHEMA_IS_INLINED("is_inlined");
const static Json::StaticString SCHEMA_UNINLINED_COLUMNS("un_inlneds_columns");

Schema::Schema(const std::vector<Column> &columns) : tuple_is_inlined_(true) {
    uint32_t curr_offset = 0;
    for (uint32_t index = 0; index < columns.size(); index++) {
        Column column = columns[index];
        // handle uninlined column
        if (!column.IsInlined()) {
            tuple_is_inlined_ = false;
            uninlined_columns_.push_back(index);
        }
        // set column offset
        column.column_offset_ = curr_offset;
        curr_offset += column.GetFixedLength();

        // add column
        this->columns_.push_back(column);
    }
    // set tuple length
    length_ = curr_offset;
}

std::string Schema::ToString() const {
    std::ostringstream os;

    os << "Schema["
       << "NumColumns:" << GetColumnCount() << ", "
       << "IsInlined:" << tuple_is_inlined_ << ", "
       << "Length:" << length_ << "]";

    bool first = true;
    os << " :: (";
    for (uint32_t i = 0; i < GetColumnCount(); i++) {
        if (first) {
            first = false;
        } else {
            os << ", ";
        }
        os << columns_[i].ToString();
    }
    os << ")";

    return os.str();
}

void Schema::to_json(Json::Value &json_value) const {
    json_value[SCHEMA_LENGTH] = length_;
    Json::Value columns;
    for (Column col : columns_) {
        Json::Value column;
        col.to_json(column);
        columns.append(std::move(column));
    }
    json_value[SCHEMA_COLUMNS] = std::move(columns);
    json_value[SCHEMA_IS_INLINED] = tuple_is_inlined_;
    Json::Value unin_columns;
    for (uint32_t t : uninlined_columns_) {
        unin_columns.append(t);
    }
    json_value[SCHEMA_UNINLINED_COLUMNS] = unin_columns;
}

bool Schema::from_json(const Json::Value &json_value, Schema &schema) {
    uint32_t length_json = json_value[SCHEMA_LENGTH].asUInt();
    const Json::Value &schema_columns = json_value[SCHEMA_COLUMNS];
    int col_nums = schema_columns.size();
    std::vector<Column> columns_json(col_nums);

    for (int i = 0; i < col_nums; i++) {
        Column &col = columns_json[i];
        const Json::Value &col_value = schema_columns[i];
        Column::from_json(col_value, col);
    }
    bool tuple_is_inlined_json = json_value[SCHEMA_IS_INLINED].asBool();

    const Json::Value &uninlined_columns = json_value[SCHEMA_UNINLINED_COLUMNS];
    col_nums = uninlined_columns.size();
    std::vector<uint32_t> uninlined_columns_json(col_nums);
    for (int i = 0; i < col_nums; i++) {
        uninlined_columns_json[i] = uninlined_columns[i].asUInt();
    }
    schema.init(length_json, columns_json, tuple_is_inlined_json, uninlined_columns_json);
}

void Schema::init(uint32_t length, std::vector<Column> columns, bool tuple_is_inlined,
                  std::vector<uint32_t> uninlined_columns) {
    length_ = length;
    columns_ = columns;
    tuple_is_inlined_ = tuple_is_inlined;
    uninlined_columns_ = uninlined_columns;
}